package com.flca.frw.config;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;

/**
 * <p>
 * Matches filenames against an Ant-style wildcard pattern list.
 * </p>
 * 
 * <p>
 * In short, ? matches one character, * matches zero or more characters but no
 * directory changes (it doesn't match / or \), and ** matches zero or more
 * directory levels. If the wildcard pattern ends in / or \, an implicit ** is
 * added.
 * </p>
 * 
 * <p>
 * Several patterns can be specified, seperated by : or ;.
 * </p>
 * 
 * <p>
 * Everything is case sensitive. If you need case insensitive pattern matching,
 * use <tt>String.toLower()</tt> on the pattern and on the candidate string.
 * </p>
 * 
 * @author Richard Cyganiak <rcyg@gmx.de>
 * @version $Id$
 */
public class FilePatternMatcher {
	private List<Pattern> patterns = new ArrayList<Pattern>();

	/**
	 * Creates a matcher to match filenames against a specified wildcard pattern
	 * 
	 * @param wildcardPatterns
	 *           an Ant-style wildcard pattern
	 */
	public FilePatternMatcher(String wildcardPatterns) {
		StringTokenizer tokenizer = new StringTokenizer(wildcardPatterns, ":;");
		while (tokenizer.hasMoreTokens()) {
			patterns.add(Pattern.compile(buildRegex(tokenizer.nextToken())));
		}
	}

	/**
	 * Matches a filename against the wildcard pattern.
	 * 
	 * @param filename
	 *           a filename
	 * @return <tt>true</tt> if the filename matches the pattern
	 */
	public boolean matches(String filename) {

		Iterator<Pattern> it = patterns.iterator();
		while (it.hasNext()) {
			Pattern regex = (Pattern) it.next();
			if (regex.matcher(useFilename(filename)).matches()) {
				return true;
			}
		}
		return false;
	}

	private String useFilename(String aFilename) {
		if (aFilename.indexOf("/") >= 0) {
			return StringUtils.replace(aFilename, "/", "\\");
		} else {
			return aFilename;
		}
	}
	
	private String buildRegex(String wildcardPatterns) {
		String temp = wildcardPatterns;
		temp = temp.replace('\\', '/');
		if (temp.endsWith("/")) {
			temp += "**";
		}
		// replace **/** with **
		temp = temp.replaceAll("\\*\\*/\\*\\*", "**");
		if ("**".equals(temp)) {
			return ".*";
		}
		// replace **/ at start with (.*/)? and /** at end with (/.*)?
		if (temp.startsWith("**/") && temp.endsWith("/**")) {
			String inner = temp.substring(3, temp.length() - 3);
			return "(.*/)?" + buildInnerRegex(inner) + "(/.*)?";
		}
		if (temp.startsWith("**/")) {
			String inner = temp.substring(3);
			return "(.*/)?" + buildInnerRegex(inner);
		}
		if (temp.endsWith("/**")) {
			String inner = temp.substring(0, temp.length() - 3);
			return buildInnerRegex(inner) + "(/.*)?";
		}
		return buildInnerRegex(temp);
	}

	private String buildInnerRegex(String wildcardPatterns) {
		// replace /**/ with /(.*/)?
		int pos = wildcardPatterns.indexOf("/**/");
		if (pos > -1) {
			String before = wildcardPatterns.substring(0, pos);
			String after = wildcardPatterns.substring(pos + 4);
			return buildInnerRegex(before) + "/(.*/)?" + buildInnerRegex(after);
		}
		// replace ? with [^/] and * with [^/]*
		return wildcardPatterns.replaceAll("\\?", "[^/]").replaceAll("\\*", "[^/]*");
	}
}
